在我們要完成 PttCrawler 這隻程式之前,我們再 review 一下 PttCrawler 的程式碼(這是上次隨意撰寫的程式碼,所以程式不一定能夠正常執行)
<?php
// src/PttCrawler.php
namespace Recca0120\Ithome30;
use Psr\Http\Client\ClientInterface;
use Recca0120\Ithome30\Crawlers\Home;
use Recca0120\Ithome30\Crawlers\Board;
class PttCrawler
{
public function __construct(private ClientInterface $httpClient)
{
}
public function all()
{
$crawler = new Home($this->httpClient);
$boardCrawler = new Board($this->httpClient);
$results = [];
foreach ($crawler->all() as $board) {
foreach ($boardCrawler->fetch($board) as $articles) {
$results[] = $articles;
}
}
return $results;
}
}
這時會發現 PttCrawler 我們要測試的會是 Home
及 Board
這兩隻 class 的一起產生出資料的情境,但我們目前寫好的 PttCrawlerTest 實際上是在測試 Home 這隻程式碼,所以 PttCrawlerTest 的測試內容是不符合情境的,經過這樣的分析,我們應該把 PttCrawlerTest 改為 HomeTest 才對,但需要重新再寫一次測試嗎?不用,我們只需 copy PttCrawlerTest 內容到 HomeTest 並修改 assertEquals 即可,所以我們會產出以下的程式碼
<?php
namespace Recca0120\Ithome30\Tests\Crawlers;
use Mockery;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use PHPUnit\Framework\TestCase;
use Recca0120\Ithome30\Crawlers\Home;
class HomeTest extends TestCase
{
public function test_fetch_board_page()
{
\VCR\VCR::turnOn();
\VCR\VCR::insertCassette('ptt_home.yaml');
/** @var Mockery\Mock|ClientInterface $httpClient */
$httpClient = Mockery::spy(new Client());
$crawler = new Home($httpClient);
$records = $crawler->all();
self::assertEquals([
'title' => '[八卦] 亞運李智凱、許皓鋐奪金!',
'url' => 'https://www.ptt.cc/bbs/Gossiping/index.html',
'name' => 'Gossiping',
'nuser' => '8803',
'class' => '綜合',
], $records[0]);
$httpClient->shouldHaveReceived('sendRequest')->once()->with(Mockery::on(function (Request $request) {
return (((string)$request->getUri()) === 'https://www.ptt.cc/bbs/hotboards.html');
}));
\VCR\VCR::eject();
\VCR\VCR::turnOff();
}
}
這樣調整完後是不是發現 PttCrawlerTest 幾乎等於沒用處了?是的!因為 PttCrawler 的程式碼內容的主體是 Home, Board 兩個 class 的互動而不是抓取頁面的過程,所以產出 HomeTest 是保護抓取首頁的過程,而 PttCrawlerTest 我們要重新撰寫測試來讓我們只需專注在 Home 及 Board 的互動!